دليل شامل لخوارزميات اجتياز الشجرة: البحث في العمق أولاً (DFS) والبحث في العرض أولاً (BFS). تعلم مبادئهما وتنفيذهما وحالات استخدامهما وخصائص الأداء.
خوارزميات اجتياز الشجرة: البحث في العمق أولاً (DFS) مقابل البحث في العرض أولاً (BFS)
في علوم الحاسوب، اجتياز الشجرة (المعروف أيضًا باسم البحث في الشجرة أو المسير في الشجرة) هو عملية زيارة (فحص و/أو تحديث) كل عقدة في هيكل بيانات الشجرة، مرة واحدة بالضبط. الأشجار هي هياكل بيانات أساسية تستخدم على نطاق واسع في تطبيقات مختلفة، من تمثيل البيانات الهرمية (مثل أنظمة الملفات أو الهياكل التنظيمية) إلى تسهيل خوارزميات البحث والفرز الفعالة. إن فهم كيفية اجتياز الشجرة أمر بالغ الأهمية للعمل معها بفعالية.
هناك طريقتان أساسيتان لاجتياز الشجرة وهما البحث في العمق أولاً (DFS) والبحث في العرض أولاً (BFS). تقدم كل خوارزمية مزايا متميزة وتناسب أنواعًا مختلفة من المشكلات. سيستكشف هذا الدليل الشامل كلاً من DFS وBFS بالتفصيل، ويغطي مبادئهما وتنفيذهما وحالات استخدامهما وخصائص الأداء.
فهم هياكل بيانات الشجرة
قبل الغوص في خوارزميات الاجتياز، دعنا نراجع بإيجاز أساسيات هياكل بيانات الشجرة.
ما هي الشجرة؟
الشجرة هي هيكل بيانات هرمي يتكون من عقد متصلة بواسطة حواف. تحتوي على عقدة جذر (العقدة العلوية)، ويمكن أن تحتوي كل عقدة على صفر أو أكثر من العقد الفرعية. تسمى العقد التي ليس لديها أبناء عقد الأوراق. تشمل الخصائص الرئيسية للشجرة ما يلي:
- الجذر: العقدة العلوية في الشجرة.
- العقدة: عنصر داخل الشجرة، يحتوي على بيانات وربما مراجع إلى العقد الفرعية.
- الحافة: الرابط بين عقدتين.
- الأب: عقدة لديها عقدة فرعية واحدة أو أكثر.
- الابن: عقدة متصلة مباشرة بعقدة أخرى (أبوها) في الشجرة.
- الورقة: عقدة ليس لديها أبناء.
- الشجرة الفرعية: شجرة تتكون من عقدة وجميع ذريتها.
- عمق العقدة: عدد الحواف من الجذر إلى العقدة.
- ارتفاع الشجرة: الحد الأقصى لعمق أي عقدة في الشجرة.
أنواع الأشجار
توجد عدة اختلافات في الأشجار، ولكل منها خصائص وحالات استخدام محددة. بعض الأنواع الشائعة تشمل:
- الشجرة الثنائية: شجرة تحتوي فيها كل عقدة على طفلين على الأكثر، ويشار إليهما عادةً بالطفل الأيسر والطفل الأيمن.
- شجرة البحث الثنائية (BST): شجرة ثنائية تكون فيها قيمة كل عقدة أكبر من أو تساوي قيمة جميع العقد في شجرتها الفرعية اليسرى وأقل من أو تساوي قيمة جميع العقد في شجرتها الفرعية اليمنى. تسمح هذه الخاصية بالبحث الفعال.
- شجرة AVL: شجرة بحث ثنائية ذاتية الموازنة تحافظ على هيكل متوازن لضمان تعقيد زمني لوغاريتمي لعمليات البحث والإدراج والحذف.
- شجرة أحمر-أسود: شجرة بحث ثنائية ذاتية الموازنة أخرى تستخدم خصائص الألوان للحفاظ على التوازن.
- شجرة N-ary (أو شجرة K-ary): شجرة يمكن أن تحتوي فيها كل عقدة على N من الأبناء على الأكثر.
البحث في العمق أولاً (DFS)
البحث في العمق أولاً (DFS) هو خوارزمية اجتياز الشجرة التي تستكشف أقصى مسافة ممكنة على طول كل فرع قبل التراجع. يعطي الأولوية للتعمق في الشجرة قبل استكشاف الأشقاء. يمكن تنفيذ DFS بشكل متكرر أو تكراري باستخدام مكدس.
خوارزميات DFS
هناك ثلاثة أنواع شائعة من اجتيازات DFS:
- اجتياز Inorder (يسار-جذر-يمين): يزور الشجرة الفرعية اليسرى، ثم عقدة الجذر، وأخيرًا الشجرة الفرعية اليمنى. يشيع استخدام هذا للأشجار البحث الثنائية لأنه يزور العقد بترتيب مرتب.
- اجتياز Preorder (جذر-يسار-يمين): يزور عقدة الجذر، ثم الشجرة الفرعية اليسرى، وأخيرًا الشجرة الفرعية اليمنى. غالبًا ما يستخدم هذا لإنشاء نسخة من الشجرة.
- اجتياز Postorder (يسار-يمين-جذر): يزور الشجرة الفرعية اليسرى، ثم الشجرة الفرعية اليمنى، وأخيرًا عقدة الجذر. يشيع استخدام هذا لحذف شجرة.
أمثلة التنفيذ (بايثون)
فيما يلي أمثلة بايثون توضح كل نوع من اجتياز DFS:
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
# Inorder Traversal (Left-Root-Right)
def inorder_traversal(root):
if root:
inorder_traversal(root.left)
print(root.data, end=" ")
inorder_traversal(root.right)
# Preorder Traversal (Root-Left-Right)
def preorder_traversal(root):
if root:
print(root.data, end=" ")
preorder_traversal(root.left)
preorder_traversal(root.right)
# Postorder Traversal (Left-Right-Root)
def postorder_traversal(root):
if root:
postorder_traversal(root.left)
postorder_traversal(root.right)
print(root.data, end=" ")
# Example Usage
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
print("Inorder traversal:")
inorder_traversal(root) # Output: 4 2 5 1 3
print("\nPreorder traversal:")
preorder_traversal(root) # Output: 1 2 4 5 3
print("\nPostorder traversal:")
postorder_traversal(root) # Output: 4 5 2 3 1
DFS التكراري (باستخدام المكدس)
يمكن أيضًا تنفيذ DFS بشكل تكراري باستخدام مكدس. فيما يلي مثال على اجتياز الطلب المسبق التكراري:
def iterative_preorder(root):
if root is None:
return
stack = [root]
while stack:
node = stack.pop()
print(node.data, end=" ")
# Push right child first so left child is processed first
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
#Example Usage (same tree as before)
print("\nIterative Preorder traversal:")
iterative_preorder(root)
حالات استخدام DFS
- إيجاد مسار بين عقدتين: يمكن لـ DFS إيجاد مسار بكفاءة في رسم بياني أو شجرة. ضع في اعتبارك توجيه حزم البيانات عبر شبكة (ممثلة كرسم بياني). يمكن لـ DFS إيجاد مسار بين خادمين، حتى إذا كانت هناك مسارات متعددة.
- الفرز الطوبولوجي: يستخدم DFS في الفرز الطوبولوجي للرسوم البيانية الدورية الموجهة (DAGs). تخيل جدولة المهام حيث تعتمد بعض المهام على مهام أخرى. يرتب الفرز الطوبولوجي المهام بترتيب يحترم هذه التبعيات.
- اكتشاف الدورات في الرسم البياني: يمكن لـ DFS اكتشاف الدورات في الرسم البياني. يعد اكتشاف الدورة مهمًا في تخصيص الموارد. إذا كان العملية A تنتظر العملية B والعملية B تنتظر العملية A، فقد يتسبب ذلك في طريق مسدود.
- حل المتاهات: يمكن استخدام DFS لإيجاد مسار عبر المتاهة.
- تحليل وتقييم التعبيرات: تستخدم المترجمات أساليب قائمة على DFS لتحليل وتقييم التعبيرات الرياضية.
مزايا وعيوب DFS
المزايا:
- بسيط التنفيذ: غالبًا ما يكون التنفيذ المتكرر موجزًا جدًا وسهل الفهم.
- فعال من حيث الذاكرة لأشجار معينة: يتطلب DFS ذاكرة أقل من BFS للأشجار المتداخلة بعمق لأنه يحتاج فقط إلى تخزين العقد على المسار الحالي.
- يمكنه إيجاد الحلول بسرعة: إذا كان الحل المطلوب عميقًا في الشجرة، فيمكن لـ DFS إيجاده أسرع من BFS.
العيوب:
- غير مضمون لإيجاد أقصر مسار: قد يجد DFS مسارًا، لكنه قد لا يكون أقصر مسار.
- احتمالية حدوث حلقات لا نهائية: إذا لم يتم تنظيم الشجرة بعناية (مثل، تحتوي على دورات)، فيمكن أن تتعثر DFS في حلقة لا نهائية.
- تجاوز المكدس: يمكن أن يؤدي التنفيذ المتكرر إلى أخطاء تجاوز المكدس للأشجار العميقة جدًا.
البحث في العرض أولاً (BFS)
البحث في العرض أولاً (BFS) هو خوارزمية اجتياز الشجرة التي تستكشف جميع العقد المجاورة على المستوى الحالي قبل الانتقال إلى العقد على المستوى التالي. يستكشف الشجرة مستوى تلو الآخر، بدءًا من الجذر. يتم تنفيذ BFS عادةً بشكل تكراري باستخدام قائمة انتظار.
خوارزمية BFS
- ضع عقدة الجذر في قائمة الانتظار.
- بينما قائمة الانتظار ليست فارغة:
- قم بإزالة عقدة من قائمة الانتظار.
- قم بزيارة العقدة (على سبيل المثال، اطبع قيمتها).
- ضع جميع أبناء العقدة في قائمة الانتظار.
مثال التنفيذ (بايثون)
from collections import deque
def bfs_traversal(root):
if root is None:
return
queue = deque([root])
while queue:
node = queue.popleft()
print(node.data, end=" ")
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
#Example Usage (same tree as before)
print("BFS traversal:")
bfs_traversal(root) # Output: 1 2 3 4 5
حالات استخدام BFS
- إيجاد أقصر مسار: BFS مضمون لإيجاد أقصر مسار بين عقدتين في رسم بياني غير موزون. تخيل مواقع التواصل الاجتماعي. يمكن لـ BFS إيجاد أقصر اتصال بين مستخدمين.
- اجتياز الرسم البياني: يمكن استخدام BFS لاجتياز الرسم البياني.
- الزحف على الويب: تستخدم محركات البحث BFS للزحف على الويب وفهرسة الصفحات.
- إيجاد أقرب الجيران: في رسم الخرائط الجغرافية، يمكن لـ BFS إيجاد أقرب المطاعم أو محطات الوقود أو المستشفيات إلى موقع معين.
- خوارزمية التعبئة بالفيضان: في معالجة الصور، يشكل BFS الأساس لخوارزميات التعبئة بالفيضان (على سبيل المثال، أداة "دلو الطلاء").
مزايا وعيوب BFS
المزايا:
- مضمون لإيجاد أقصر مسار: يجد BFS دائمًا أقصر مسار في رسم بياني غير موزون.
- مناسب لإيجاد أقرب العقد: BFS فعال لإيجاد العقد القريبة من عقدة البداية.
- يتجنب الحلقات اللانهائية: نظرًا لأن BFS يستكشف مستوى تلو الآخر، فإنه يتجنب التعثر في حلقات لا نهائية، حتى في الرسوم البيانية التي تحتوي على دورات.
العيوب:
- مكثف الذاكرة: يمكن أن يتطلب BFS الكثير من الذاكرة، خاصة بالنسبة للأشجار العريضة، لأنه يحتاج إلى تخزين جميع العقد على المستوى الحالي في قائمة الانتظار.
- يمكن أن يكون أبطأ من DFS: إذا كان الحل المطلوب عميقًا في الشجرة، فيمكن أن يكون BFS أبطأ من DFS لأنه يستكشف جميع العقد في كل مستوى قبل التعمق.
مقارنة DFS و BFS
فيما يلي جدول يلخص الاختلافات الرئيسية بين DFS و BFS:
| الميزة | البحث في العمق أولاً (DFS) | البحث في العرض أولاً (BFS) |
|---|---|---|
| ترتيب الاجتياز | يستكشف أقصى مسافة ممكنة على طول كل فرع قبل التراجع | يستكشف جميع العقد المجاورة على المستوى الحالي قبل الانتقال إلى المستوى التالي |
| التنفيذ | متكرر أو تكراري (باستخدام المكدس) | تكراري (باستخدام قائمة الانتظار) |
| استخدام الذاكرة | ذاكرة أقل بشكل عام (للأشجار العميقة) | ذاكرة أكبر بشكل عام (للأشجار العريضة) |
| أقصر مسار | غير مضمون لإيجاد أقصر مسار | مضمون لإيجاد أقصر مسار (في الرسوم البيانية غير الموزونة) |
| حالات الاستخدام | إيجاد المسار، الفرز الطوبولوجي، اكتشاف الدورة، حل المتاهة، تحليل التعبيرات | إيجاد أقصر مسار، اجتياز الرسم البياني، الزحف على الويب، إيجاد أقرب الجيران، التعبئة بالفيضان |
| خطر الحلقات اللانهائية | خطر أعلى (يتطلب تنظيمًا دقيقًا) | خطر أقل (يستكشف مستوى تلو الآخر) |
الاختيار بين DFS و BFS
يعتمد الاختيار بين DFS و BFS على المشكلة المحددة التي تحاول حلها وخصائص الشجرة أو الرسم البياني الذي تعمل معه. فيما يلي بعض الإرشادات لمساعدتك في الاختيار:
- استخدم DFS عندما:
- تكون الشجرة عميقة جدًا وتشتبه في أن الحل يقع في الأسفل.
- يمثل استخدام الذاكرة مصدر قلق كبير، والشجرة ليست عريضة جدًا.
- تحتاج إلى اكتشاف الدورات في الرسم البياني.
- استخدم BFS عندما:
- تحتاج إلى إيجاد أقصر مسار في رسم بياني غير موزون.
- تحتاج إلى إيجاد أقرب العقد إلى عقدة البداية.
- الذاكرة ليست قيدًا رئيسيًا، والشجرة عريضة.
ما وراء الأشجار الثنائية: DFS و BFS في الرسوم البيانية
على الرغم من أننا ناقشنا DFS و BFS بشكل أساسي في سياق الأشجار، إلا أن هذه الخوارزميات قابلة للتطبيق بالتساوي على الرسوم البيانية، وهي هياكل بيانات أكثر عمومية حيث يمكن أن يكون للعقد اتصالات عشوائية. تظل المبادئ الأساسية كما هي، ولكن قد تدخل الرسوم البيانية دورات، مما يتطلب اهتمامًا إضافيًا لتجنب الحلقات اللانهائية.
عند تطبيق DFS و BFS على الرسوم البيانية، من الشائع الاحتفاظ بمجموعة أو مصفوفة "تمت زيارتها" لتتبع العقد التي تم استكشافها بالفعل. يمنع هذا الخوارزمية من إعادة زيارة العقد والتعثر في الدورات.
الخلاصة
البحث في العمق أولاً (DFS) والبحث في العرض أولاً (BFS) هما خوارزميات أساسية لاجتياز الشجرة والرسم البياني بخصائص وحالات استخدام متميزة. إن فهم مبادئهما وتنفيذهما والمفاضلات في الأداء أمر ضروري لأي عالم كمبيوتر أو مهندس برمجيات. من خلال التفكير بعناية في المشكلة المحددة المطروحة، يمكنك اختيار الخوارزمية المناسبة لحلها بكفاءة. بينما يتفوق DFS في كفاءة الذاكرة واستكشاف الفروع العميقة، يضمن BFS إيجاد أقصر مسار ويتجنب الحلقات اللانهائية، مما يجعل من الضروري فهم الاختلافات بينهما. إن إتقان هذه الخوارزميات سيعزز مهاراتك في حل المشكلات ويسمح لك بمعالجة تحديات هيكل البيانات المعقدة بثقة.